package com.zhss.demo.register.client;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.*;
/**
 * 服务注册中心的客户端缓存的一个服务注册表
 *
 * @author zhonghuashishan
 */
public class ClientCachedServiceRegistry {

    /**
     * 服务注册表拉取间隔时间
     */
    private static final Long SERVICE_REGISTRY_FETCH_INTERVAL = 30 * 1000L;

    /**
     * 客户端缓存的服务注册表
     */
    private AtomicStampedReference<Applications> applications = new AtomicStampedReference<Applications>(new Applications(), 0);
    /**
     * 负责定时拉取注册表到客户端进行缓存的后台线程
     */
    private FetchDeltaRegistryWorker fetchDeltaRegistryWorker;
    /**
     * RegisterClient
     */
    private RegisterClient registerClient;
    /**
     * http通信组件
     */
    private HttpSender httpSender;

    /**
     * description 针对applications的读写锁
     * author 周成龙
     * createTime 2021/1/29 9:23
     **/
    private ReentrantReadWriteLock applicationslock=new ReentrantReadWriteLock();
    private ReadLock applicationsReadLock=applicationslock.readLock();
    private WriteLock applicationsWriteLock=applicationslock.writeLock();

    private AtomicLong applicationVersion = new AtomicLong(0L);

    public ClientCachedServiceRegistry(RegisterClient registerClient, HttpSender httpSender) {
        this.fetchDeltaRegistryWorker = new FetchDeltaRegistryWorker();
        this.registerClient = registerClient;
        this.httpSender = httpSender;
    }

    /**
     * 初始化服务注册表
     * 全量拉去服务注册表
     */
    public void initialize() {
        //全量拉去服务注册表
        FetchFullRegisterWorker fetchFullRegisterWorker = new FetchFullRegisterWorker();
        fetchFullRegisterWorker.start();
        try {
            fetchFullRegisterWorker.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //TODO 开启拉取增量服务注册表
        this.fetchDeltaRegistryWorker.start();
    }

    /**
     * 全量拉去注册表后台线程
     */
    private class FetchFullRegisterWorker extends Thread {
        @Override
        public void run() {
			/*synchronized (applications){
				applications=httpSender.fetchFullServiceRegistry();
			}*/
            // 一定要在发起网络请求之前，先拿到一个当时的版本号
            // 接着在这里发起网络请求，此时可能会有别的线程来修改这个注册表，更新版本，在这个期间
            // 必须是发起网络请求之后，这个注册表的版本没有被人修改过，此时他才能去修改
            // 如果在这个期间，有人修改过注册表，版本不一样了，此时就直接if不成立，不要把你拉取到的
            // 旧版本的注册表给设置进去
            fetchFullRegister();
        }
    }

    /**
     * 拉取全量注册表
     */
    public void fetchFullRegister() {
        while (true) {
            //在网络请求之前，获取版本号
            long exceptedVersion = applicationVersion.get();
            //发起网络请求拉取全量注册表
            Applications fetchApplications = httpSender.fetchFullServiceRegistry();
            //TODO 这里还是有点问题的  如果不能保证下列操作的原子性，还是可能导致先进来的卡主了，后进来的先修改，造成版本错乱
            if (applicationVersion.compareAndSet(exceptedVersion, exceptedVersion + 1)) {
                //获取当前值
                Applications expectedApplications = applications.getReference();
                //获取时间戳
                int expectedStamp = applications.getStamp();
                //再从applications中获取applications的值
                //与expectedApplications比较，如果相等就更新值
                //如果不相等就返回false，继续循环
                if (applications.compareAndSet(expectedApplications, fetchApplications, expectedStamp, expectedStamp + 1)) {
                    return;
                }
            }
        }
    }

    /**
     * 拉取增量服务注册表
     */
    private class FetchDeltaRegistryWorker extends Thread {
        @Override
        public void run() {
            while (registerClient.isRunning()) {
                try {
                    //在网络请求之前，获取版本号
                    long exceptedVersion = applicationVersion.get();
                    //拉取增量表
                    DeltaRegister deltaRegister = httpSender.fetchDeltaRegistry();
                    // 将增量表合并到注册表中
                    if (applicationVersion.compareAndSet(exceptedVersion, exceptedVersion + 1)) {
                        mergeDelateRegister(deltaRegister.getRecentlyChangeServiceInstances());
                        //在检查一下，和服务器的注册表的服务实例的数据对比一下是否一致
                        //封装一下增量注册表的对象，也就是拉取注册表的时候，一方面返回那个数据
                        //另一方面，是要那个对应的register-server端的服务实例的数量
                        reconcileRegister(deltaRegister);
                    }
                    //休眠一段时间
                    Thread.sleep(SERVICE_REGISTRY_FETCH_INTERVAL);
                } catch (Exception e) {
                    System.out.printf(e.toString());
                } finally {
                    System.out.printf("拉取注册表，本地注册表为：" + applications.getReference().getRegister().toString());
                }
            }
        }

        /**
         * 校对拉取的增量表和server中的数量是否一致
         *
         * @param deltaRegister
         */
        private void reconcileRegister(DeltaRegister deltaRegister) {
            //todo
            //在检查一下，和服务器的注册表的服务实例的数据对比一下是否一致
            //封装一下增量注册表的对象，也就是拉取注册表的时候，一方面返回那个数据
            //另一方面，是要那个对应的register-server端的服务实例的数量
            Long serverRegisterInstancesTotal = deltaRegister.getRegisterInstancesTotal();
            Long clientRegisterInstancesTotal = 0L;
            for (Map<String, ServiceInstance> serviceInstanceMap : applications.getReference().getRegister().values()) {
                clientRegisterInstancesTotal += serviceInstanceMap.size();
            }
            if (serverRegisterInstancesTotal != clientRegisterInstancesTotal) {
                System.out.println("注册表更新出错，拉取全量注册表");
                fetchFullRegister();
            }
        }

        /**
         * 合并增量数据表
         *
         * @param recentlyChangedServiceInstances
         */
        public void mergeDelateRegister(LinkedList<RecentlyChangedServiceInstance> recentlyChangedServiceInstances) {
            try {
                applicationsWriteLock.lock();
                RecentlyChangedServiceInstance recentlyChangedServiceInstance = null;
                while ((recentlyChangedServiceInstance = recentlyChangedServiceInstances.peek()) != null) {
                    ServiceInstance serviceInstance = recentlyChangedServiceInstance.getServiceInstance();
                    if (ServiceInstanceOperation.REGISTER.equals(recentlyChangedServiceInstance.getServiceInstanceOperation())) {
                        //注册服务，插入
                        Map<String, ServiceInstance> stringServiceInstanceMap = applications.getReference().getRegister().get(serviceInstance.getServiceName());
                        if (stringServiceInstanceMap == null) {
                            stringServiceInstanceMap = new HashMap<String, ServiceInstance>();
                            applications.getReference().getRegister().put(serviceInstance.getServiceName(), stringServiceInstanceMap);
                        }
                        ServiceInstance serviceInstance1 = stringServiceInstanceMap.get(serviceInstance.getServiceInstanceId());
                        if (serviceInstance1 == null) {
                            stringServiceInstanceMap.put(serviceInstance.getServiceInstanceId(), serviceInstance);
                        }
                    } else if (ServiceInstanceOperation.REMOVE.equals(recentlyChangedServiceInstance.getServiceInstanceOperation())) {
                        //移除服务
                        Map<String, ServiceInstance> stringServiceInstanceMap = applications.getReference().getRegister().get(serviceInstance.getServiceName());
                        if (stringServiceInstanceMap != null) {
                            stringServiceInstanceMap.remove(serviceInstance.getServiceInstanceId());
                        }
                    }
                    recentlyChangedServiceInstances.removeFirst();
                }
            }finally {
                applicationsWriteLock.unlock();
            }
        }
    }

    /**
     * 销毁这个组件
     */
    public void destroy() {
        this.fetchDeltaRegistryWorker.interrupt();
    }

    /**
     * 负责定时拉取注册表到本地来进行缓存
     *
     * @author zhonghuashishan
     */
    private class Daemon extends Thread {

        @Override
        public void run() {
            while (registerClient.isRunning()) {
                try {
//					applications = httpSender.fetchFullServiceRegistry();
                    Thread.sleep(SERVICE_REGISTRY_FETCH_INTERVAL);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 获取服务注册表
     *
     * @return
     */
    public Map<String, Map<String, ServiceInstance>> getRegistry() {
        try {
            applicationsReadLock.lock();
            return applications.getReference().getRegister();
        }finally {
            applicationsReadLock.unlock();
        }

    }


    /**
     * 最近变化服务实例
     */
    static class RecentlyChangedServiceInstance {

        /**
         * 服务实例
         */
        ServiceInstance serviceInstance;
        /**
         * 发生变更的时间戳
         */
        Long changedTimestamp;
        /**
         * 变更操作
         */
        String serviceInstanceOperation;

        public RecentlyChangedServiceInstance(
                ServiceInstance serviceInstance,
                Long changedTimestamp,
                String serviceInstanceOperation) {
            this.serviceInstance = serviceInstance;
            this.changedTimestamp = changedTimestamp;
            this.serviceInstanceOperation = serviceInstanceOperation;
        }

        public ServiceInstance getServiceInstance() {
            return serviceInstance;
        }

        public void setServiceInstance(ServiceInstance serviceInstance) {
            this.serviceInstance = serviceInstance;
        }

        public Long getChangedTimestamp() {
            return changedTimestamp;
        }

        public void setChangedTimestamp(Long changedTimestamp) {
            this.changedTimestamp = changedTimestamp;
        }

        public String getServiceInstanceOperation() {
            return serviceInstanceOperation;
        }

        public void setServiceInstanceOperation(String serviceInstanceOperation) {
            this.serviceInstanceOperation = serviceInstanceOperation;
        }

        @Override
        public String toString() {
            return "RecentlyChangedServiceInstance [serviceInstance=" + serviceInstance + ", changedTimestamp="
                    + changedTimestamp + ", serviceInstanceOperation=" + serviceInstanceOperation + "]";
        }

    }

    /**
     * 服务操作
     */
    class ServiceInstanceOperation {
        public static final String REGISTER = "REGISTER";
        public static final String REMOVE = "REMOVE";
    }
}
